﻿using System;
using NUnit.Framework;
using NUnit.Framework.SyntaxHelpers;
using Rhino.Mocks;
using SimpleServiceBus.Bus;
using SimpleServiceBus.Bus.Pipeline;
using SimpleServiceBus.Endpoint;
using SimpleServiceBus.Tests.Stubs;

namespace SimpleServiceBus.Tests.Bus.Pipeline
{
    [TestFixture]
    public class MessagePipelineTestFixture
    {
        private IMessagingEndpoint _ep;

        [TestFixtureSetUp]
        public void SetUp()
        {
            _ep = MockRepository.GenerateStub<IMessagingEndpoint>();
        }


        [Test]
        [ExpectedException(typeof (InvalidOperationException))]
        public void Cannot_Process_Two_Receive_Pipelines_On_Same_Thread()
        {
            var pipeline = new MessagePipeline();

            try
            {
                pipeline.Initialize(new MessageEnvelope(), _ep, PipelineDirection.Receive);

                var pipeline2 = new MessagePipeline();
                pipeline2.Initialize(new MessageEnvelope(), _ep, PipelineDirection.Receive);
            }
            finally
            {
                pipeline.ProcessMessage();
            }
        }

        [Test]
        [ExpectedException(typeof (InvalidOperationException))]
        public void Cannot_Process_Two_Send_Pipelines_On_Same_Thread()
        {
            var pipeline = new MessagePipeline();
            try
            {
                pipeline.Initialize(new MessageEnvelope(), _ep, PipelineDirection.Send);

                var pipeline2 = new MessagePipeline();
                pipeline2.Initialize(new MessageEnvelope(), _ep, PipelineDirection.Send);
            }
            finally
            {
                pipeline.ProcessMessage();
            }
        }

        [Test]
        public void Initialize_Sets_Singleton_And_Context()
        {
            var pipeline = new MessagePipeline();
            Assert.That(MessagePipeline.CurrentReceivePipeline, Is.Null);

            pipeline.Initialize(new MessageEnvelope(), _ep, PipelineDirection.Receive);

            Assert.That(MessagePipeline.CurrentReceivePipeline, Is.SameAs(pipeline));

            Assert.That(MessagePipeline.CurrentSendPipeline, Is.Null);

            var sendPipeline = new MessagePipeline();
            sendPipeline.Initialize(new MessageEnvelope(), _ep, PipelineDirection.Send);

            Assert.That(MessagePipeline.CurrentSendPipeline, Is.SameAs(sendPipeline));

            pipeline.ProcessMessage();
            sendPipeline.ProcessMessage();

            Assert.That(MessagePipeline.CurrentReceivePipeline, Is.Null);
            Assert.That(MessagePipeline.CurrentSendPipeline, Is.Null);
        }

        [Test]
        public void Pipeline_Status_Follows_Lifecycle()
        {
            var pipeline = new MessagePipeline();
            PipelineStatus processingStatus = PipelineStatus.Uninitialized;

            Assert.That(pipeline.Status, Is.EqualTo(PipelineStatus.Uninitialized));

            var message = new MessageEnvelope
                              {
                                  Body = new TestMessage()
                              };

            var component = MockRepository.GenerateStub<IMessagePipelineComponent>();

            component.Stub(action => action.ProcessMessage(message)).Callback(delegate(MessageEnvelope env)
                                                                                  {
                                                                                      processingStatus = pipeline.Status;
                                                                                      return true;
                                                                                  });
            component.Stub(action => action.EnabledFor(message)).Return(true);
            component.Enabled = true;

            pipeline.Initialize(message, _ep, PipelineDirection.Receive, component);

            Assert.That(pipeline.Status, Is.EqualTo(PipelineStatus.Initialized));

            pipeline.ProcessMessage();

            Assert.That(processingStatus, Is.EqualTo(PipelineStatus.Processing));
            Assert.That(pipeline.Status, Is.EqualTo(PipelineStatus.Complete));
        }

        [Test]
        public void Process_Pipeline_Components_And_Respect_Abort_And_Respect_Disabled()
        {
            var pipeline = new MessagePipeline();

            var message = new MessageEnvelope {Body = 0};

            //Normal
            IMessagePipelineComponent c1 = GenerateComponentStub(pipeline, message, false,false,false);
            IMessagePipelineComponent c2 = GenerateComponentStub(pipeline, message, false,false,false);
            
            //Enabled = false
            IMessagePipelineComponent c3 = GenerateComponentStub(pipeline, message, false, true, false);
            
            //EnabledFor(message) = false
            IMessagePipelineComponent c4 = GenerateComponentStub(pipeline, message, false, false, true);
            
            //Abort = true
            IMessagePipelineComponent c5 = GenerateComponentStub(pipeline, message, true,false,false);
            
            //Normal
            IMessagePipelineComponent c6 = GenerateComponentStub(pipeline, message, false,false,false);

            pipeline.Initialize(message, _ep, PipelineDirection.Receive, c1, c2, c3, c4, c5, c6);

            pipeline.ProcessMessage();

            Assert.That((int) message.Body, Is.EqualTo(3));
        }

        [Test]
        public void Pipeline_Fires_Appropriate_Events()
        {
            var pipeline = new MessagePipeline();
            
            using (var events = new EventRecorder(pipeline.PipelineEvents))
            {
                var message = new MessageEnvelope {Body = 0};

                IMessagePipelineComponent c1 = GenerateComponentStub(pipeline, message, false,false,false);
                IMessagePipelineComponent c2 = GenerateComponentStub(pipeline, message, false,false,false);
                IMessagePipelineComponent c3 = GenerateComponentStub(pipeline, message, false,false,false);

                pipeline.Initialize(message, _ep, PipelineDirection.Receive, c1, c2, c3);

                Assert.That(events.EventHistory.Length, Is.EqualTo(3));

                Assert.That(((MessagePipelineStatusChangedEventArgs)events.EventHistory[0].Args).PreviousStatus, Is.EqualTo(PipelineStatus.Uninitialized));
                Assert.That(((MessagePipelineStatusChangedEventArgs)events.EventHistory[0].Args).CurrentStatus, Is.EqualTo(PipelineStatus.Initializing));

                events.ClearEventHistory();

                pipeline.ProcessMessage();

                Assert.That(events.EventHistory.Length, Is.EqualTo(10));
                Assert.That(events.GetInvocationsFor("PipelineInitialized").Length, Is.EqualTo(0));
                Assert.That(events.GetInvocationsFor("PipelineStatusChanged").Length,Is.EqualTo(2));

                Assert.That(events.EventHistory[events.EventHistory.Length - 1].EventName,
                            Is.EqualTo("PipelineCompleted"));
              

                Assert.That(events.GetInvocationsFor("ProcessingComponentStarted").Length,
                            Is.EqualTo(pipeline.PipelineComponents.Count));


            }
        }

        /// <summary>
        /// Generates the component stub.
        /// </summary>
        /// <param name="pipeline">The pipeline.</param>
        /// <param name="msg">The MSG.</param>
        /// <param name="abortProcessing">if set to <c>true</c> [abort processing].</param>
        /// <param name="disableFromProperty"></param>
        /// <param name="disableFromMethod"></param>
        /// <returns></returns>
        private static IMessagePipelineComponent GenerateComponentStub(IMessagePipeline pipeline, MessageEnvelope msg,
                                                                     bool abortProcessing,bool disableFromProperty,bool disableFromMethod)
        {
            var component = MockRepository.GenerateStub<IMessagePipelineComponent>();

            component.Stub(action => action.ProcessMessage(msg)).Callback(delegate(MessageEnvelope env)
            {
                env.Body = (int)env.Body + 1;
                pipeline.CurrentContext.
                    AbortMessageProcessing =
                    abortProcessing;
                return true;
            });

            component.Stub(action => action.EnabledFor(msg)).Return(!disableFromMethod);
            component.Enabled = !disableFromProperty;

            return component;
        }

        
    }
    
}